/*
 * JSF Toolkit Component Framework
 * Copyright (C) 2007 Noah Sloan <iamnoah A-T gmail D0T com>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 */
package com.jsftoolkit.base.components;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import javax.faces.component.UIComponent;
import javax.faces.component.UIData;
import javax.faces.context.FacesContext;

import com.jsftoolkit.base.JsfIterator;
import com.jsftoolkit.base.ResourceInfo;

/**
 * Callback used in conjunction with
 * {@link #iterate(FacesContext, JsfIterator, UIDataProcessor)}.
 * {@link UIDataProcessor} is a misnomer since
 * {@link #iterate(FacesContext, JsfIterator, UIDataProcessor)} can handle any
 * {@link JsfIterator}.
 * 
 * @author noah
 * 
 */
public abstract class UIDataProcessor {

	/**
	 * Process the {@link JsfIterator} BEFORE any iteration occurs. The default
	 * behavior is to do nothing.
	 * 
	 * @param context
	 * @param data
	 *            the {@link JsfIterator} to process.
	 */
	public void preprocess(FacesContext context, JsfIterator data) {
		// do nothing
	}

	/**
	 * Called at the start of every row. By default, this method does nothing.
	 * 
	 * @param context
	 * @param data
	 */
	public void startRow(FacesContext context, JsfIterator data) {
		// do nothing
	}

	/**
	 * Called for every facet for each row, although the order of the facets is
	 * unspecified. The default behavior is to ignore any facets.
	 * 
	 * @param context
	 * @param facet
	 * @param name
	 */
	public void processFacet(FacesContext context, UIComponent facet,
			String name) {
		// do nothing
	}

	/**
	 * Processes the next child of the UIData component. Called for each child
	 * (in order), for each row. Does nothing by default.
	 * 
	 * @param context
	 * @param child
	 * @throws IOException
	 */
	public void processChild(FacesContext context, UIComponent child) {
		// do nothing
	}

	/**
	 * Called at the end of each row, after the facets and children are
	 * processed. By default, this method does nothing.
	 * 
	 * @param context
	 * @param data
	 */
	public void endRow(FacesContext context, JsfIterator data) {
		// do nothing
	}

	/**
	 * Called at the end of iteration. The row index will be whatever it was
	 * before iteration began. This method is not called if an unchecked
	 * exception occurs during iteration. By default, this method does nothing.
	 * 
	 * @param context
	 * @param data
	 */
	public void postprocess(FacesContext context, JsfIterator data) {
		// do nothing
	}

	/**
	 * Iterates over the given UIData, letting the callback process.
	 * <p>
	 * This code will have no side effects when it finishes, although the
	 * callback might.
	 * <p>
	 * The callback methods are invoked in the following order:
	 * <ol>
	 * <li>{@link #preprocess(FacesContext, JsfIterator)}</li>
	 * <li>For each row
	 * <ol>
	 * <li>{@link #startRow(FacesContext, JsfIterator)}</li>
	 * <li>For each facet
	 * {@link #processFacet(FacesContext, UIComponent, String)} is called</li>
	 * <li>For each child {@link #processChild(FacesContext, UIComponent)} is
	 * called</li>
	 * <li>{@link #endRow(FacesContext, JsfIterator)}</li>
	 * </ol>
	 * </li>
	 * <li>{@link #postprocess(FacesContext, JsfIterator)}</li>
	 * </ol>
	 * 
	 * @param context
	 * @param data
	 * @param callback
	 * @throws IOException
	 */
	public static void iterate(FacesContext context, JsfIterator data,
			UIDataProcessor callback) {
		// save the current index
		final int originalIndex = data.getRowIndex();

		final int first = data.getFirst(); // first row requested
		final int requested = data.getRows(); // # rows requested

		try {
			callback.preprocess(context, data);

			// requested == 0 means render all remaining rows
			for (int count = 0; requested == 0 || count < requested; count++) {
				// set the current row
				data.setRowIndex(first + count);
				if (!data.isRowAvailable()) {
					break;
					// no more rows. This is the only way to get out of the
					// loop if requested is 0
				}

				// inform the callback that we've started a new row
				callback.startRow(context, data);

				// process the facets
				for (Entry<String, UIComponent> entry : data.getFacets()
						.entrySet()) {
					UIComponent facet = entry.getValue();
					resetIds(facet);
					callback.processFacet(context, facet, entry.getKey());
				}

				// let the children encode themselves
				for (UIComponent child : data.getChildren()) {
					resetIds(child);
					callback.processChild(context, child);
				}

				// inform the callback that we're at the end of a row
				callback.endRow(context, data);
			}
		} catch (RuntimeException e) {
			callback.handleException(context, data, e);
		} finally {
			// reset the UIData to where it was before
			data.setRowIndex(originalIndex);
		}
		callback.postprocess(context, data);
	}

	/**
	 * Called if a {@link RuntimeException} was thrown while iterating. This
	 * method does not need to worry about reseting the row index. By default,
	 * this method rethrows the exception.
	 * 
	 * @param context
	 * @param data
	 * @param e
	 */
	public void handleException(FacesContext context, JsfIterator data,
			RuntimeException e) {
		throw e;
	}

	/**
	 * Resets root and calls {@link #resetChildIds(UIComponent)}.
	 * 
	 * @param root
	 */
	public static void resetIds(UIComponent root) {
		root.setId(root.getId());
		resetChildIds(root);
	}

	/**
	 * Recursively resets the ids of the children of the given component. i.e.
	 * it clears the cached id value for components so that calls to
	 * {@link UIComponent#getClientId(FacesContext)} return the correct id.
	 * <p>
	 * <code>root</code> is not reset.
	 * 
	 * @param root
	 */
	public static void resetChildIds(UIComponent root) {
		List<UIComponent> children = root.getChildren();
		for (UIComponent child : children) {
			if (child.isRendered()) {
				child.setId(child.getId());
				resetChildIds(child);
			}
		}
	}

	/**
	 * Wraps {@link UIData} so it conforms to {@link JsfIterator}.
	 * 
	 * @author noah
	 * 
	 */
	public static class UIDataWrapper implements JsfIterator {

		private final UIData wrapped;

		public UIDataWrapper(UIData wrapped) {
			super();
			this.wrapped = wrapped;
		}

		public UIData getWrapped() {
			return wrapped;
		}

		public Map<String, UIComponent> getFacets() {
			return wrapped.getFacets();
		}

		public List<UIComponent> getChildren() {
			return wrapped.getChildren();
		}

		public int getFirst() {
			return wrapped.getFirst();
		}

		public int getRowIndex() {
			return wrapped.getRowIndex();
		}

		public int getRows() {
			return wrapped.getRows();
		}

		public boolean isRowAvailable() {
			return wrapped.isRowAvailable();
		}

		public void setRowIndex(int rowIndex) {
			wrapped.setRowIndex(rowIndex);
		}

		public Set<ResourceInfo> getResources(FacesContext context,
				UIComponent component) {
			throw new UnsupportedOperationException(
					"UIDataWrapper cannot be used for finding resources.");
		}

	}
}